#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>

#define SERV_PORT 8755
#define MAXLINE 32
#define	MAX(a,b) ((a) > (b) ? (a) : (b))
#define error_exit(msg) \
	do { perror(msg); exit(EXIT_FAILURE); } while (0)

ssize_t writen(int fd, const void *vptr, size_t n) {
	size_t		nleft;
	ssize_t		nwritten;
	const char	*ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0; /* and call write() again */
			else
				return(-1);	
		}

		nleft -= nwritten;
		ptr   += nwritten;
	}
	return n;
}

ssize_t readline(int fd, void *vptr, size_t maxlen) {
	ssize_t	n, rc;
	char c, *ptr;

	ptr = vptr;
	for (n = 1; n < maxlen; n++) {
again:
		if ((rc = read(fd, &c, 1)) == 1) { // 一次读一个字节，对于大量数据将执行非常慢
			*ptr++ = c;
			if (c == '\n')
				break; // newline is stored, like fgets()
		} else if (rc == 0) {
			*ptr = 0;
			return (n-1); // EOF, n-1 bytes were read
		} else {
			if (errno == EINTR)
				goto again;
			return (-1); 
		}
	}
	
	*ptr = 0; 
	return(n);
}

void doit(FILE *fp, int sockfd) {
	char sendline[MAXLINE], recvline[MAXLINE];

	/* 1.当服务器进程终止（RST）时，客户进程可能由于正阻塞于fgets而未接收到该通知。（select或poll函数可处理这种情形） 
	 *   导致第一次写操作引发RST，第二次写操作引发SIGPIPE信号，该信号默认行为是终止进程。
	 *   （规则：当一个进程向某个已收到RST的套接字执行写操作时，内核向该进程发送一个SIGPIPE信号。）
	 * 2.服务器主机崩溃的情形要等到客户主动向服务器发送了数据才能检测到。（或者使用SO_KEEPALIVE套接字选项） 
	 * 3.服务器主机关机的情形类型与“1” */
	signal(SIGPIPE, SIG_IGN); // 更改SIGPIPE的行为为忽略，使客户不会终止在write函数上。
	while (fgets(sendline, MAXLINE, fp) != NULL) { // 从标准输入读入一行文本
		writen(sockfd, sendline, 1); 
		writen(sockfd, sendline+1, strlen(sendline)-1); // writen保证发送strlen(sendline)-1个字节，否则失败

		if (readline(sockfd, recvline, MAXLINE) == 0)
			error_exit("server terminated prematurely");

		fputs(recvline, stdout); // 写服务端回射行到标准输出
	}
}

void doit_select(FILE *fp, int sockfd) {
	char buf[MAXLINE];
	int maxfdp1, stdineof;
	fd_set rset;
	int n;

	signal(SIGPIPE, SIG_IGN);
	stdineof = 0;
	FD_ZERO(&rset);
	for ( ; ; ) {
		if (stdineof == 0)
			FD_SET(fileno(fp), &rset);
		FD_SET(sockfd, &rset);
		maxfdp1 = MAX(fileno(fp), sockfd) + 1;
		if (select(maxfdp1, &rset, NULL, NULL, NULL) < 0)
			error_exit("select");;
	
		/* 改用read和write对缓冲区进行操作，使得select能够如期地工作 */
		if (FD_ISSET(sockfd, &rset)) { // sockfd可读
			if ((n = read(sockfd, buf, MAXLINE)) == 0) { // read读完接收缓存区数据后返回0
				if (stdineof == 1) // 如果已处理完所有客户请求就正常返回
					return;	/* normal termination */
				else
					error_exit("server terminated prematurely");
			}
			write(fileno(stdout), buf, n);
		}
		if (FD_ISSET(fileno(fp), &rset)) { // input可读
			if ((n = read(fileno(fp), buf, MAXLINE)) == 0) { // read读到文件尾时返回0
				stdineof = 1; // fp描述符到达文件尾
				FD_CLR(fileno(fp), &rset); // slect不再等待fp描述符可读事件				
				shutdown(sockfd, SHUT_WR); // 关闭套接字写一半，发送FIN 
				continue;
			}
			writen(sockfd, buf, n); // SHUT_WR后所有写函数不可用
		}
	}
}

static char* gf_time(void) {
	struct timeval tv;
	static char str[30];
	char *ptr;
	
	if (gettimeofday(&tv, NULL) < 0)
		error_exit("gettimeofday error\n");
	
	ptr = ctime(&tv.tv_sec);
	strcpy(str, &ptr[11]);
	snprintf(str+8, sizeof(str)-8, ".%06ld", tv.tv_usec);
	return str;
}

void doit_select_nonb(FILE *fp, int sockfd) {
	int maxfdp1, val, stdineof;
	ssize_t n, nwritten;
	fd_set rset, wset;
	char to[MAXLINE]; // to缓冲区容纳从标准输入（stdin）到服务器（sockfd）的数据
	char fr[MAXLINE]; // fr缓冲区容纳自服务器（sockfd）到标准输出（stdout）的数据
	char *toiptr;     // toiptr指向从标准输入读入的数据可以存放的下一个字节，可从stdin读入的字节数是（&to[MAXLINE]-toiptr）
	char *tooptr;     // tooptr指向下一个必须写到套接字的字节，有（toiptr-tooptr）个字节需写到sockfd
	char *friptr;     // friptr
	char *froptr;     // froptr

	/* 把描述符设置为非阻塞 */
	val = fcntl(sockfd, F_GETFL, 0);
	fcntl(sockfd, F_SETFL, val | O_NONBLOCK);

	val = fcntl(STDIN_FILENO, F_GETFL, 0);
	fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);

	val = fcntl(STDOUT_FILENO, F_GETFL, 0);
	fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);

	/* 初始化缓冲区指针 */
	toiptr = tooptr = to;
	friptr = froptr = fr;
	stdineof = 0;

	/* 准备调用select */
	maxfdp1 = MAX(MAX(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
	for ( ; ; ) {
		FD_ZERO(&rset);
		FD_ZERO(&wset);
		if (stdineof == 0 && toiptr < &to[MAXLINE]) // 在stdin上尚未读到EOF，而且to缓冲区有至少一个字节可用空间
			FD_SET(STDIN_FILENO, &rset);	
		if (friptr < &fr[MAXLINE]) // fr缓冲区至少一个字节可用空间
			FD_SET(sockfd, &rset);	
		if (tooptr != toiptr) // to缓冲区中有要写到套接字的数据
			FD_SET(sockfd, &wset);	
		if (froptr != friptr) // fr缓冲区中有要写到标准输出的数据
			FD_SET(STDOUT_FILENO, &wset);	

		/* 调用select */
		select(maxfdp1, &rset, &wset, NULL, NULL);

		/* 从标准输入read */
		if (FD_ISSET(STDIN_FILENO, &rset)) {
			if ( (n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {
				if (errno != EWOULDBLOCK) // 忽略非阻塞错误EWOULDBLOCK（select已告知可读，read实际上不会返回此错误）
					error_exit("read error on stdin");
			} else if (n == 0) { // 如果read返回0，那么设置stdineof标记结束标准输入处理
				stdineof = 1;
				if (tooptr == toiptr) // 如果to缓冲区不再有数据要发送，那么调用shutdown发送FIN
					shutdown(sockfd, SHUT_WR);
			} else {
				printf("%s: read %d bytes from stdin\n", gf_time(), n);

				toiptr += n;
				FD_SET(sockfd, &wset);
			}
		}

		/* 从套接字read */		
		if (FD_ISSET(sockfd, &rset)) {
			if ( (n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) {
				if (errno != EWOULDBLOCK)
					error_exit("read error on socket");
			} else if (n == 0) {
				if (stdineof)
					return;	
				else
					error_exit("str_cli: server terminated prematurely");
			} else {
				fprintf(stderr, "%s: read %d bytes from socket\n", gf_time(), n);

				friptr += n;
				FD_SET(STDOUT_FILENO, &wset);
			}
		}

		/* write到标准输出 */			
		if (FD_ISSET(STDOUT_FILENO, &wset) && ((n = friptr - froptr) > 0)) { // stdout可写且要写的字节数大于0
			if ( (nwritten = write(STDOUT_FILENO, froptr, n)) < 0) {
				if (errno != EWOULDBLOCK)
					error_exit("write error to stdout");
			} else {
				fprintf(stderr, "%s: wrote %d bytes to stdout\n", gf_time(), nwritten);

				froptr += nwritten;
				if (froptr == friptr) // 一旦输出（froptr）追上输入（friptr），则两个指针同时恢复为指向缓冲区开始处
					froptr = friptr = fr;
			}
		}

		/* write到套接字 */
		if (FD_ISSET(sockfd, &wset) && ((n = toiptr - tooptr) > 0)) { 
			if ((nwritten = write(sockfd, tooptr, n)) < 0) {
				if (errno != EWOULDBLOCK)
					error_exit("write error to socket");
			} else {
				fprintf(stderr, "%s: wrote %d bytes to socket\n", gf_time(), nwritten);

				tooptr += nwritten;
				if (tooptr == toiptr) { // 一旦tooptr移动到toiptr，这两个指针就一起恢复到缓冲区开始处
					toiptr = tooptr = to;
					if (stdineof) // 如果已经在标准输入上遇到EOF就发送FIN到服务器
						shutdown(sockfd, SHUT_WR);	/* send FIN */
				}
			}
		}
	}
}

void doit_fork(FILE *fp, int sockfd) {
	pid_t pid;
	char sendline[MAXLINE], recvline[MAXLINE];

	/* 父子进程共享同一个套接字，接收缓冲区和发送缓冲区也分别只有一个，而有两个描述符在引用这个套接字 */
	if ((pid = fork()) == 0) {		
		while (readline(sockfd, recvline, MAXLINE) > 0)
			fputs(recvline, stdout);

		kill(getppid(), SIGTERM); // 服务器过早终止时，子进程向父进程发送一个SIGTERM信号，以防止父进程仍在运行
		exit(0);
	}

	while (fgets(sendline, MAXLINE, fp) != NULL)
		writen(sockfd, sendline, strlen(sendline));

	shutdown(sockfd, SHUT_WR);
	pause();
	return;
}

static int g_sockfd;
static FILE *g_fp;
static int g_done;

void* copyto(void *arg) {
	char sendline[MAXLINE];

	while (fgets(sendline, MAXLINE, g_fp) != NULL)
		writen(g_sockfd, sendline, strlen(sendline));

	shutdown(g_sockfd, SHUT_WR);	/* EOF on stdin, send FIN */

	g_done = 1;
	return(NULL);
}

void doit_thread(FILE *fp_arg, int sockfd_arg) {
	char recvline[MAXLINE];
	pthread_t tid;

	g_sockfd = sockfd_arg;
	g_fp = fp_arg;

	pthread_create(&tid, NULL, copyto, NULL);

	while (readline(g_sockfd, recvline, MAXLINE) > 0)
		fputs(recvline, stdout);

	if (g_done == 0)
		error_exit("server terminated prematurely");
}

int main(int argc, char **argv) {
	int sockfd;
	struct sockaddr_in servaddr;

	if (argc != 2)
		error_exit("usage: tcpcli <IPaddress>");

	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
	connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	//doit(stdin, sockfd); // 客户的所有请求
	doit_thread(stdin, sockfd); // 客户的所有请求

	exit(0); // 进程终止时会自动关闭所有由内核打开的描述符——close(sockfd)此时自动调用
}


